// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/url_request/url_request_backoff_manager.h"

#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "net/http/http_response_headers.h"

namespace net {

const uint16_t URLRequestBackoffManager::kMinimumBackoffInSeconds = 1;
const uint16_t URLRequestBackoffManager::kMaximumBackoffInSeconds = 50000;
const uint16_t URLRequestBackoffManager::kNewEntriesBetweenCollecting = 200;

URLRequestBackoffManager::URLRequestBackoffManager()
    : new_entries_since_last_gc_(0)
{
    url_id_replacements_.ClearPassword();
    url_id_replacements_.ClearUsername();
    url_id_replacements_.ClearQuery();
    url_id_replacements_.ClearRef();

    NetworkChangeNotifier::AddIPAddressObserver(this);
    NetworkChangeNotifier::AddConnectionTypeObserver(this);
}

URLRequestBackoffManager::~URLRequestBackoffManager()
{
    NetworkChangeNotifier::RemoveIPAddressObserver(this);
    NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
    for (UrlEntryMap::iterator it = url_entries_.begin();
         it != url_entries_.end(); ++it) {
        delete it->second;
    }
    url_entries_.clear();
}

void URLRequestBackoffManager::UpdateWithResponse(
    const GURL& url,
    HttpResponseHeaders* headers,
    const base::Time& response_time)
{
    CalledOnValidThread();
    base::TimeDelta result;
    if (GetBackoffTime(headers, &result)) {
        new_entries_since_last_gc_++;
        std::string url_id = GetIdFromUrl(url);
        auto it = url_entries_.find(url_id);
        if (it != url_entries_.end())
            delete it->second;
        url_entries_[url_id] = new Entry(response_time + result, response_time + result * 1.1);
        GarbageCollectEntriesIfNecessary();
    }
}

bool URLRequestBackoffManager::ShouldRejectRequest(
    const GURL& url,
    const base::Time& request_time)
{
    CalledOnValidThread();
    std::string url_id = GetIdFromUrl(url);
    UrlEntryMap::iterator it = url_entries_.find(url_id);
    if (it == url_entries_.end())
        return false;
    Entry* entry = it->second;
    if (request_time < entry->throttled_time)
        return true;
    // Allow one request between throttled_time and release_time.
    if (request_time >= entry->throttled_time && request_time < entry->release_time) {
        if (entry->used)
            return true;
        entry->used = true;
    }
    return false;
}

void URLRequestBackoffManager::OnIPAddressChanged()
{
    OnNetworkChange();
}

void URLRequestBackoffManager::OnConnectionTypeChanged(
    NetworkChangeNotifier::ConnectionType type)
{
    OnNetworkChange();
}

int URLRequestBackoffManager::GetNumberOfEntriesForTests() const
{
    return url_entries_.size();
}

void URLRequestBackoffManager::GarbageCollectEntriesIfNecessary()
{
    CalledOnValidThread();
    if (new_entries_since_last_gc_ < kNewEntriesBetweenCollecting)
        return;

    new_entries_since_last_gc_ = 0;
    UrlEntryMap::iterator it = url_entries_.begin();
    while (it != url_entries_.end()) {
        Entry* entry = it->second;
        if (entry->IsOutDated()) {
            url_entries_.erase(it++);
            delete entry;
        } else {
            ++it;
        }
    }
}

bool URLRequestBackoffManager::GetBackoffTime(HttpResponseHeaders* headers,
    base::TimeDelta* result) const
{
    base::StringPiece name("Backoff");
    std::string value;
    size_t iter = 0;
    while (headers->EnumerateHeader(&iter, name, &value)) {
        int64_t seconds;
        base::StringToInt64(value, &seconds);
        if (seconds >= kMinimumBackoffInSeconds && seconds <= kMaximumBackoffInSeconds) {
            *result = base::TimeDelta::FromSeconds(seconds);
            return true;
        }
    }
    return false;
}

std::string URLRequestBackoffManager::GetIdFromUrl(const GURL& url) const
{
    if (!url.is_valid())
        return url.possibly_invalid_spec();

    GURL id = url.ReplaceComponents(url_id_replacements_);
    return base::ToLowerASCII(id.spec());
}

void URLRequestBackoffManager::OnNetworkChange()
{
    CalledOnValidThread();

    new_entries_since_last_gc_ = 0;
    // Remove all entries.
    for (UrlEntryMap::iterator it = url_entries_.begin();
         it != url_entries_.end(); ++it) {
        delete it->second;
    }
    url_entries_.clear();
}

} // namespace net
